package dm_pm;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.Vector;

/**
 * This is the main class of the application. Its responsibilities are to parse the 
 * program input arguments, generate appropriate task sets to simulate, simulate them,
 * and report the results to a CSV file for further analysis.
 * 
 * @author Tom Deering
 * @author Dossay Oryspayev
 * 
 */
public class Simulation implements Runnable{
	public static final int MASTER_THREAD_SLEEP_MILLIS = 1000;
	public static final boolean TEST_MODE = true;
	private static float results[];
	private static Random rand;
	static{
		rand = new Random();
		rand.setSeed(System.currentTimeMillis());
	}
	
	private double uMin, uMax;
	private long pMin, pMax;
	private double pDelta;
	private double uSys;
	private int numCPUs;
	private long maxSimLength;
	private long repetitions;
	private int resultIdx;
	private boolean done;
	
	/**
	 * Main method and program entry point
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		System.out.println("Starting... "+getCurrentTimeStamp());
		//Check for bad program usage
		if(args.length!=2){
			IO.logError("Program accepts exactly two arguments: <INPUT_FILE.csv> <OUTPUT_FILE.csv>");
			return;
		}
		
		//Read the parameter sets from the input file
		Vector<String []> inputs;
		try {
			inputs = IO.readFromCSV(args[0]);
		} catch (FileNotFoundException e) {
			IO.logError("Input file specified does not exist.");
			return;
		} catch (IOException e){
			IO.logError("Problem reading from input file.");
			return;
		} catch (Exception e){
			IO.logError(e.getMessage());
			return;
		}

		//Create the simulations from the parameter sets
		Simulation[] sims;
		try {
			sims = createSimulations(inputs);
		} catch (Exception e) {
			IO.logError(e.getMessage());
			return;
		}

		//Run all simulations with ideal parallelism
		executeSimulations(sims);
		
		//Write the results of the simulations to CSV
		String[][] output = new String[results.length][1];
		for(int i=0; i<results.length; i++){
			output[i][0] = Float.toString(results[i]);
		}
		try {
			IO.writeToCSV(args[1], output);
		} catch (Exception e) {
			IO.logError(e.getMessage());
			return;
		}
		
		System.out.println("Finished... "+getCurrentTimeStamp());		
	}
	
	public static String getCurrentTimeStamp() {
	    SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
	    Date now = new Date();
	    String strDate = sdfDate.format(now);
	    return strDate;
	}

	
	/**
	 * Constructs a new simulation with the given parameters.
	 * 
	 * @param uMin Minimum allowed task utilization Ci/Pi
	 * @param uMax Maximum allowed task utilization Ci/Pi
	 * @param pMin Minimum allowed task period
	 * @param pMax Maximum allowed task period
	 * @param pDelta Maximum sporadic variation of task periods, in units of multiples of the period
	 * @param uSys System utilization for the simulation
	 * @param numCPUs Number of CPUs for the simulation
	 * @param maxSimLength Maximum tick count for the simulation
	 * @param repetitions The number of times to generate and schedule task sets with the same set of parameters
	 * @param resultIdx The index in the result array where this simulation should write its success ratio
	 */
	public Simulation(double uMin, double uMax, long pMin, long pMax, double pDelta, double uSys, int numCPUs, long maxSimLength, long repetitions, int resultIdx){
		this.uMin = uMin;
		this.uMax = uMax;
		this.pMin = pMin;
		this.pMax = pMax;
		this.pDelta = pDelta;
		this.uSys = uSys;
		this.numCPUs = numCPUs;
		this.maxSimLength = maxSimLength;
		this.repetitions = repetitions;
		this.resultIdx = resultIdx;
		this.done = false;
	}

	@Override
	public void run() {
		int R = 0;
		int S = 0;
		
		while(R<this.repetitions) {
			Vector<Task> V_t = generateTasks(
					uMin, uMax, pMin, pMax, pDelta, uSys, numCPUs);
			Vector<Processor> V_p = new Vector<Processor>();
			
			for(int i = 0; i<numCPUs; i++) {
				Processor proc = new Processor();
				V_p.add(proc);
			}
		
			algorithmDM scheduler = new algorithmDM(
					V_t, V_p, this.maxSimLength, (int) this.pDelta);
			try {
				if(scheduler.startAlgorithm()==RESULT.SUCCESS){
					S +=1;
				}				
			}
			catch(IllegalArgumentException iae) {
				System.err.println(iae.getMessage());
			}
			R += 1;
		}
		results[resultIdx] = ((float) S)/((float) R);
		IO.logInfo("Simulation " + resultIdx + " finished!");
		synchronized(this) {
			done = true;
		}
	}
	

	/**
	 * Creates a set of task "recipes" for the simulation based upon the task and system parameters.
	 * 
	 * @param uMin Minimum allowed task utilization Ci/Pi
	 * @param uMax Maximum allowed task utilization Ci/Pi
	 * @param pMin Minimum allowed task period
	 * @param pMax Maximum allowed task period
	 * @param pDelta Maximum sporadic variation of task periods, in units of multiples of the period
	 * @param uSys System utilization for the simulation
	 * @param numCPUs Number of CPUs for the simulation
	 * @return A vector of tasks used as "recipes" for generating instances in the simulation
	 */
	private static Vector<Task> generateTasks(double uMin, double uMax, long pMin, long pMax, double pDelta, double uSys, int numCPUs){
		double multicoreUtilization = uSys*numCPUs;//Total utilization of the system
		double generatedUtilization = 0;//Count of utilization generated so far
		Vector<Task> tasks = new Vector<Task>();//Vector of the tasks generated
		
		//Temporary variables allocated outside loop for efficiency
		double randBound;
		double utilization;
		long computation, period, deadline;
		
		//Generate enough tasks to reach the desired system utilization
		while(multicoreUtilization-generatedUtilization >= uMin){

			//Uniformly pick utilization between uMin and min{uMax, multicoreUtlization-generatedUtilization}
			randBound = uMax>(multicoreUtilization-generatedUtilization)?(multicoreUtilization-generatedUtilization):uMax;
			utilization = uMin + rand.nextDouble()%(randBound-uMin);
			
			//Uniformly pick period between pMin and pMax
			period = pMin + Math.abs(rand.nextLong())%(pMax-pMin);
			
			//Calculate the appropriate computation time (truncated)
			computation = (long) (period*utilization);
			
			//Uniformly pick a deadline between max{period-pDelta*period,computation} and period
			randBound = period-pDelta*period>=computation?period-pDelta*period:computation;
			if(period-(int)randBound==0){
				deadline = period;
			}else{
				deadline = (long) (randBound + Math.abs(rand.nextLong())%(period-(int)randBound));
			}

			//Update the utilization generated so far
			generatedUtilization += ((double)computation)/((double)period);
			
			//Add the task to the task vector
			Task t = new Task(computation, period, deadline);
			tasks.add(t);
			//IO.logInfo(t.toString());
		}
		//Allow one last task with utilization less than uMin to get as close to uSys as possible
		if(multicoreUtilization-generatedUtilization > 0){
			//Fill up utilization exactly to desired amount
			utilization = multicoreUtilization-generatedUtilization;
			
			//Pick the largest period so system utilization is as close as possible to desired
			period = pMax;
			
			//Calculate the appropriate computation time (truncated)
			computation = (long) (period*utilization);
			
			if(computation>0){
				//Uniformly pick a deadline between max{period-pDelta*period,computation} and period
				randBound = period-pDelta*period>computation?period-pDelta*period:computation;
				deadline = (long) (randBound + Math.abs(rand.nextLong())%(period-randBound));
				
				//Update the utilization generated so far
				generatedUtilization += ((double)computation)/((double)period);
				
				//Add the task to the task vector
				tasks.add(new Task(computation, period, deadline));
			}
		}
		
		//IO.logInfo("Task set has multicore utilization " +generatedUtilization+" while requested was "+multicoreUtilization);
		return tasks;
	}
	
	/**
	 * Returns whether or not the current simulation is done.
	 * 
	 * @return True if the simulation is done, false otherwise
	 */
	public boolean checkFinished(){
		synchronized(this){
			return done;
		}
	}
	
	/**
	 * Based on the inputs given, create the simulations
	 * 
	 * @param inputs Input instructions for simulations
	 * @return Array of simulations to execute
	 * @throws Exception
	 */
	private static Simulation[] createSimulations(Vector<String[]> inputs) throws Exception{
		Simulation[] sims = new Simulation[inputs.size()];
		results = new float[inputs.size()];
		for(int i=0; i<results.length; i++){
			results[i] = -1;
		}

		for(int i=0; i<inputs.size(); i++){
			String[] sArray = inputs.elementAt(i);
			if(sArray.length!=9){
				throw new Exception("Each input file line must be exactly 9 CSV parameters!");
			}
			double uMin = Double.valueOf(sArray[0]);
			double uMax = Double.valueOf(sArray[1]);
			long pMin = Long.valueOf(sArray[2]);
			long pMax = Long.valueOf(sArray[3]);
			double pDelta = Double.valueOf(sArray[4]);
			double uSys = Double.valueOf(sArray[5]);
			int numCPUs = Integer.valueOf(sArray[6]);
			long maxSimLength = Long.valueOf(sArray[7]);
			long repetitions = Long.valueOf(sArray[8]);
			sims[i] = new Simulation(uMin, uMax, pMin, pMax, pDelta, uSys, numCPUs, maxSimLength, repetitions, i);
		}
				
		IO.logInfo("Created " + sims.length + " simulations from input.");
		return sims;
	}
	
	/**
	 * Execute all simulations, keeping the number of simulation threads
	 * equal to the number of cpus.
	 * 
	 * @param sims The array of simulations to run
	 */
	private static void executeSimulations(Simulation[] sims){
		int numCPUs = Runtime.getRuntime().availableProcessors();
		IO.logInfo(numCPUs + " CPUs are available to the Java VM.");
		Vector<Simulation> toExecute = new Vector<Simulation>(sims.length);
		for(Simulation s : sims){
			toExecute.add(s);
		}
		Vector<Simulation> executing = new Vector<Simulation>(numCPUs);
		Vector<Simulation> toRemove = new Vector<Simulation>(numCPUs);
		
		//Finish executing all simulations
		while(toExecute.size()!=0 || executing.size()!=0){
			//Remove finished simulations
			toRemove.clear();
			for(Simulation s : executing){
				if(s.checkFinished())toRemove.add(s);
			}
			executing.removeAll(toRemove);
			
			//Start more simulations
			toRemove.clear();
			for(int i=0; executing.size()<numCPUs && i<toExecute.size(); i++){
				Simulation s = toExecute.elementAt(i);
				toRemove.add(s);
				executing.add(s);
				IO.logInfo("Starting simulation "+ s.resultIdx);
				(new Thread(s)).start();
			}
			toExecute.removeAll(toRemove);
			
			//Check again after sleeping
			try {Thread.sleep(MASTER_THREAD_SLEEP_MILLIS);} 
			catch (InterruptedException e) {}
		}
	}
	
	public long getMaxSimLength() {
		return maxSimLength;
	}

	public void setMaxSimLength(long maxSimLength) {
		this.maxSimLength = maxSimLength;
	}

	public long getRepetitions() {
		return repetitions;
	}

	public void setRepetitions(long repetitions) {
		this.repetitions = repetitions;
	}

	public String toString() {
		return "Simulation = [uMin: " + this.uMin+";"+
		       "uMax: " + this.uMax + "; " +
		       "pMin: " + this.pMin + "; " +
		       "pMax: " + this.pMax + ";\n" +
		       "pDelta: " + this.pDelta + "; " +
		       "uSys: " + this.uSys + "; " +
		       "numCPUs: " + this.numCPUs + "; " +
		       "maxSimLength: " + this.maxSimLength + ";\n" +
		       "repetitions: " + this.repetitions + "; " +
		       "resultIdx:" + this.resultIdx + "]";
	}
	
}
